﻿# encoding: UTF-8

"""
在onTick中开平仓时，不报错测试
"""

from ctaBase import *
from ctaTemplate import *
import datetime
import numpy as np
import pandas as pd


class MaCollectBreak(CtaTemplate):
    """均线粘合突破策略"""

    className = 'MaCollectBreak'
    author = 'WZC'
    name = ''  # 策略实例名称

    # 参数映射表
    paramMap = {
        'exchange': '交易所',
        'vtSymbol': '合约',
        'nMin': 'K线分钟',
        'orderIDlimit': '订单时间限制',
        'npriceTick': '止损差',
        'mpriceTick': '止赢差',
        'volume': '下单手数',
        'istime': '是否选择时间段',
        # 'macduplimit':'macd上限',
        # 'macdlowlimit': 'macd下限',
        # 'up_low': '上下限为收盘价千分之',
        'isUi': '是否显示K线图',
        'startdt': '开始时间',
        'enddt': '结束时间',
        'startdt1': '夜盘开始时间',
        'enddt1': '夜盘结束时间',
        'istlimit': '是否限制次数',
        'tlimit': '次数限制',
    }

    # 参数列表，保存了参数的名称
    paramList = list(paramMap.keys())

    # 变量映射表
    varMap = {
        'trading': '交易中',
        'pos': '持仓',
        'ma_10': '前根K线10日均线'
    }

    # 变量列表，保存了变量的名称
    varList = list(varMap.keys())

    # ----------------------------------------------------------------------
    def __init__(self, ctaEngine=None, setting={}):
        """Constructor"""
        super().__init__(ctaEngine, setting)

        self.vtSymbol = 'FG209'
        self.exchange = 'CZCE'
        self.macduplimit = 1.5
        self.macdlowlimit = -1.5

        self.widgetClass = KLWidget
        self.widget = None
        self.trading = False
        self.isUi = False
        self.cost = 0
        self.tick = None
        self.tradeDate = None  # 当前交易日期
        self.timeTick = None
        self.tickRestrainSignal = 0
        self.up_low = 2
        self.orderIDlimit = 11
        self.orderIDlimitX = 0
        self.ma_10 = 0

        self.openshortPos = 0
        self.openbuyPos = 0
        self.openbuyPos = 0
        self.buyOpenPrice = 0
        self.shortOpenPrice = 0
        self.openavgprice = 0  # 开仓均价
        self.npriceTick = 20
        self.mpriceTick = 25
        self.priceTick = 0
        self.excTimes = 0
        self.istime = True
        self.startdt = '09:10:00'
        self.enddt = '14:45:00'
        self.startdt1 = '21:10:00'
        self.enddt1 = '22:45:00'
        self.istlimit = False
        self.tlimit = 1
        self.buySignal = False
        self.shortSignal = False
        self.buyP = 0
        self.shortP = 0
        self.sksk = 0

        self.nMin = 5
        self.volume = 1  # 下单手数

        # 启动界面
        self.signal = 0  # 买卖标志
        self.mainSigs = ['ma_5', 'ma_10', 'ma_20']  # 主图显示
        self.subSigs = []  # 副图显示
        self.getGui()

        # 注意策略类中的可变对象属性（通常是list和dict等），在策略初始化时需要重新创建，
        # 否则会出现多个策略实例之间数据共享的情况，有可能导致潜在的策略逻辑错误风险，
        # 策略类中的这些可变对象属性可以选择不写，全都放在__init__下面，写主要是为了阅读
        # 策略时方便（更多是个编程习惯的选择）

    # ----------------------------------------------------------------------
    def onTick(self, tick):
        """收到行情TICK推送"""
        self.tick = tick
        super().onTick(tick)
        # 过滤涨跌停和集合竞价
        if tick.lastPrice == 0 or tick.askPrice1 == 0 or tick.bidPrice1 == 0:
            return
        self.bm.updateTick(tick)

        """Tick中止损止赢"""
        # 平多

        if self.openbuyPos > 0 and self.tickRestrainSignal ==0 and self.timeTick !=self.tick.time:
            # 止赢  如果最新价-减持仓价大于m*priceTick 或者 最新价小于上一根10日均线，则平仓
            if self.mpriceTick * self.priceTick <= (self.tick.lastPrice - self.buyOpenPrice) or self.tick.lastPrice < self.ma10[-1]:
                self.orderID = self.sell(self.tick.bidPrice1, self.openbuyPos)
                self.tickRestrainSignal = 1
                self.timeTick = self.tick.time

                # 止损   限价或者 最新价小于上一根10日均线
            elif self.npriceTick * self.priceTick <= (self.buyOpenPrice - self.tick.lastPrice) or self.tick.lastPrice < self.ma10[-1]:
                self.orderID = self.sell(self.tick.bidPrice1, self.openbuyPos)
                self.tickRestrainSignal = 1
                self.timeTick = self.tick.time

        # 平空
        elif self.openshortPos < 0 and self.tickRestrainSignal == 0 and self.timeTick !=self.tick.time:
            # 止赢  限价 或 最新价大于上根10日均线，则止赢
            if self.mpriceTick * self.priceTick <= (self.shortOpenPrice - self.tick.lastPrice) or self.tick.lastPrice > self.ma10[-1]:
                self.orderID = self.cover(self.tick.askPrice1, -self.openshortPos)
                self.tickRestrainSignal = 1
                self.timeTick = self.tick.time
            # 止损  限价 或 最新价大于上根10日均线，则止损
            elif self.npriceTick * self.priceTick <= (self.tick.lastPrice - self.shortOpenPrice) or self.tick.lastPrice > self.ma10[-1]:
                self.orderID = self.cover(self.tick.askPrice1, -self.openshortPos)
                self.tickRestrainSignal = 1
                self.timeTick = self.tick.time

    def onBar(self, bar):
        """收到Bar推送（必须由用户继承实现）"""

        self.bar = bar
        # 挂单未成交,超过限定的周期内未成交，则取消
        if self.orderID is not None and self.orderIDlimit:
            self.orderIDlimit -= 1
            if self.orderIDlimit == 0:
                self.cancelOrder(self.orderID)
                self.orderIDlimit = self.orderIDlimitX

        if self.tradeDate != bar.date:
            self.tradeDate = bar.date
        # 在1分钟周期内，查询持仓，为平仓函数调用
        self.manage_position(1)
        # 在1分中查询持仓，供ontick中调用self.openbuyPos和self.openshortPos
        if self.tpos0L[self.vtSymbol] > self.ypos0L[self.vtSymbol]:
            self.openbuyPos = self.tpos0L[self.vtSymbol]
        elif self.tpos0L[self.vtSymbol] < self.ypos0L[self.vtSymbol]:
            self.openbuyPos = self.ypos0L[self.vtSymbol]
        elif self.ypos0S[self.vtSymbol] > self.tpos0S[self.vtSymbol]:
            self.openshortPos = -self.ypos0S[self.vtSymbol]
        elif self.ypos0S[self.vtSymbol] < self.tpos0S[self.vtSymbol]:
            self.openshortPos = -self.tpos0S[self.vtSymbol]

        if self.trading:
            self.putEvent()

        # 记录数据
        if not self.bm.updateBar(bar):
            return

    def onBarX(self, bar):
        """收到Bar推送（必须由用户继承实现）"""
        self.bar = bar
        # 记录数据
        if not self.am.updateBar(bar):
            return
        self.longPrice = bar.close
        self.shortPrice = bar.close

         # 计算指标和信号

        self.getCtaIndictorSig1(bar)
        # self.output(f'self.openbuyPos {self.openbuyPos},self.ma10[-1] {self.ma10[-1]}')
        # 简易信号执行
        self.execSignal(self.volume)

        # 发出状态更新事件
        if (not self.widget is None) and (not self.bar is None):
            data = {'bar': self.bar, 'sig': self.signal, 'ma_5': self.ma_5, 'ma_10': self.ma_10, 'ma_20': self.ma_20}
            self.widget.addBar(data)
        if self.trading:
            self.putEvent()

    def getCtaIndictorSig1(self, bar):
        """均线粘合突破策略，计算指标数据，及交易信号"""
        # 计算指标数值
        self.ma_5 = self.am.sma(5)
        self.ma_10 = self.am.sma(10)
        self.ma_20 = self.am.sma(20)

        self.ma5 = self.am.sma(5, True)
        self.ma10 = self.am.sma(10, True)
        self.ma20 = self.am.sma(20, True)
        self.ma80 = self.am.sma(80, True)

        v1 = self.am.volume[-1] / np.mean(self.am.volume[-6:-1])
        maxMa = max(self.ma5[-1], self.ma10[-1], self.ma20[-1])
        minMa = min(self.ma5[-1], self.ma10[-1], self.ma20[-1])
        self.buyP = max(self.am.high[-11:-1:1])     # 开多仓价格,调整为前10天最高价
        self.shortP = min(self.am.low[-11:-1:1])   # 开空仓价格，调整为前10天最低价

        # 阴阳线的实体要大于收盘价的4/1000
        xx0 = abs(self.am.open[-1]-self.am.close[-1]) > self.am.close[-1]*4/1000
        # 5、10、20日均线，最大值减最小值，小于等于4，说明均线粘合
        xx = maxMa-minMa <= 4
        #  收盘价大于前10日最高价
        xx1 = self.am.close[-1] >= max(self.am.high[-11:-1:1])
        xx2 = self.am.close[-1] <= min(self.am.low[-11:-1:1])
        self.buySig = xx0 and xx and xx1 and v1 > 1.2
        self.shortSig = xx0 and xx and xx2

    def execSignal(self, volume):
        """简易交易信号执行"""
        self.signal = 0

        if datetime.datetime.now() - datetime.timedelta(minutes=1) > self.bar.datetime:  # 加载历史数据时执行
            # 买开，卖开
            if self.shortSig:
                self.signal = -self.shortPrice
                self.orderID = self.short(self.shortP, volume)
                self.output(f'时间：{self.bar.datetime} 卖出开空仓价格：{self.shortP}')

            elif self.buySig:
                self.signal = self.longPrice
                self.orderID = self.buy(self.buyP, volume)
                self.output(f'时间：{self.bar.datetime} 买入开多仓价格：{self.buyP}')
        else:
            # # 没有接到行情
            if self.trading and self.tick is None:
                self.output('行情未初始化')
                return

            # 次数超限
            elif self.istlimit and self.excTimes > self.tlimit:
                self.output('交易次数超限')
                return
                # 时间不满足
            elif self.istime and self.tick and not ((self.sTime <= self.tick.datetime.time() <= self.eTime) or (
                    self.sTime1 <= self.tick.datetime.time() <= self.eTime1)):
                self.output(f'{self.bar.datetime} 非交易时间段')
                return

            self.manage_position(1)  # 在信号周期内，查询持仓
            pos = self.pos[self.vtSymbol]

            # 挂单未成交
            # if self.orderID is not None:
            #     self.cancelOrder(self.orderID)
            # 当前无仓位   self.sksk为限制次开仓变量
            if pos == 0 and self.orderID is None:
                # 买开，卖开
                if self.shortSig and self.sksk == 0:
                    self.signal = -self.shortPrice
                    self.sksk += 1
                    self.orderID = self.short(self.shortP, volume)
                    self.output(f'时间：{self.bar.datetime} 卖出开空仓价格：{self.shortP}')
                    self.shortOpenPrice = self.shortP
                    if self.trading:
                        self.excTimes += 1

                elif self.buySig and self.sksk == 0:
                    self.signal = self.longPrice
                    self.sksk += 1
                    self.orderID = self.buy(self.buyP, volume)
                    self.output(f'时间：{self.bar.datetime} 买入开多仓价格：{self.buyP}')
                    self.buyOpenPrice = self.buyP
                    if self.trading:
                        self.excTimes += 1
            #     # 多头平仓，收盘价小于10日均线（止赢），另外，止损在Ontick中
            # elif (self.tpos0L[self.vtSymbol] > 0 or self.ypos0L[self.vtSymbol] > 0) and self.am.close[-1] < self.ma10[-1]:
            #     if self.tpos0L[self.vtSymbol] > self.ypos0L[self.vtSymbol]:
            #         self.orderID = self.sell(self.am.close[-1]-1, self.tpos0L[self.vtSymbol])
            #     elif self.tpos0L[self.vtSymbol] < self.ypos0L[self.vtSymbol]:
            #         self.orderID = self.sell(self.am.close[-1] - 1,  self.ypos0L[self.vtSymbol])
            #
            #     # 空头平仓，收盘价大于10日均线（止赢），另外，止损在Ontick中
            # elif (self.ypos0S[self.vtSymbol] > 0 or self.tpos0S[self.vtSymbol] > 0) and self.am.close[-1] > self.ma10[-1]:
            #     if self.ypos0S[self.vtSymbol] > self.tpos0S[self.vtSymbol]:
            #         self.orderID = self.cover(self.am.close[-1] + 1, self.ypos0S[self.vtSymbol])
            #     elif self.ypos0S[self.vtSymbol] < self.tpos0S[self.vtSymbol]:
            #         self.orderID = self.cover(self.am.close[-1] + 1, self.tpos0S[self.vtSymbol])

    def onInit(self):
        """本策略刚加载的时候不需要显示 QT 界面"""
        super().onInit()
        self.closeGui()

    def onTrade(self, trade, log=True):
        self.sksk = 0
        self.tickRestrainSignal = 0
        super().onTrade(trade, log)

        # 下面的代码可以用于校验 持仓情况
        if trade.direction == '多':
            if trade.offset == '开仓':
                self.openbuyPos = trade.volume
                self.openbuyPos_to = trade.volume  # 今天开多仓
                self.buyOpenPrice = trade.price
            elif trade.offset == '平今' or trade.offset == '平昨':
                self.openshortPos += trade.volume       # 空头，在此进行持仓处理

        elif trade.direction == '空':
            if trade.offset == '开仓':
                self.openshortPos = -trade.volume
                self.openshortPos_to = -trade.volume  # 今开空仓
                self.shortOpenPrice = trade.price
            elif trade.offset == '平今' or trade.offset == '平昨':
                self.openbuyPos -= trade.volume     # 多头，在此进行持仓处理

    def onStart(self):
        self.orderIDlimitX = self.orderIDlimit
        self.bm = BarManager(self.onBar, self.nMin, self.onBarX)
        self.am = ArrayManager(size=100)
        try:
            self.loadBar(10)
        except (TypeError, ValueError) as e:
            self.output(e)
            self.onStop()
            return StatusCode.stop
        super().onStart()

        self.sTime = datetime.datetime.strptime(self.startdt, '%H:%M:%S').time()
        self.eTime = datetime.datetime.strptime(self.enddt, '%H:%M:%S').time()
        self.sTime1 = datetime.datetime.strptime(self.startdt1, '%H:%M:%S').time()
        self.eTime1 = datetime.datetime.strptime(self.enddt1, '%H:%M:%S').time()

        # 获取品种的最好小变动价位，供止损止赢调用
        ndata = ctaEngine.getInstrument(self.exchange, self.vtSymbol)
        self.priceTick = ndata["PriceTick"]  # 品种最小价位变动
        # self.output(f'priceTick:{type(self.priceTick)}')
        # 开始时，查询持仓一次 多仓或空仓的数量
        getInvestorPosition_List = self.getInvestorPosition(self.get_investor())
        for kk in getInvestorPosition_List:
            if kk['InstrumentID'] == self.vtSymbol and kk['Direction'] == '多':
                self.buyOpenPrice = kk['OpenAvgPrice']    # 多单，开仓价格
                self.openbuyPos = kk['Position']  # 总持仓
                # self.openbuyPos_ye = kk['YdPositionClose']  # 昨持仓
                # self.openbuyPos_to = kk['Position'] - kk['YdPositionClose']  # 今持仓

            elif kk['InstrumentID'] == self.vtSymbol and kk['Direction'] == '空':
                self.shortOpenPrice = kk['OpenAvgPrice'] # 空仓 开仓价格
                self.openshortPos = -kk['Position']  # 总持仓
                # self.openshortPos_ye = -kk['YdPositionClose']  # 昨持仓
                # self.openshortPos_to = kk['Position'] - kk['YdPositionClose']  # 今持仓

        if self.isUi:
            self.getGui()

    def onStop(self):
        self.trading = False
        super().onStop()
        if not self.widget is None:
            self.widget.clear()
            self.closeGui()




